/**
 * 自动化编辑器纯代码版本自动斗地主
 * 出牌规则基于douzero_api实现https://gitee.com/toast_1234/douzero_api 
 * 
 */
var base_url = "http://192.168.31.199:8090/"; //修改为自己的服务器地址
var key = "test_key"; //每一个key对应一局游戏

const PLAYER = 0; //玩家位置
const PLAYER_DOWN = 1; //玩家下家位置
const PLAYER_UP = 2; //玩家上家位置

var all_cards = ['大王', '小王', '2', 'A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3'];

RealCard2EnvCard = { '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 11, 'Q': 12, 'K': 13, 'A': 14, '2': 17, '小王': 20, '大王': 30 };
EnvCard2RealCard = { 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q', 13: 'K', 14: 'A', 17: '2', 20: '小王', 30: '大王' };

/**
 * identify_landlord - 识别当前游戏中谁是地主。
 *
 * 该函数通过调用 `image.find` 方法依次查找玩家、下家和上家的地主标识图片，
 * 如果找到对应的地主标识，则返回对应玩家的位置常量（PLAYER、PLAYER_DOWN 或 PLAYER_UP）。
 * 如果未找到任何地主标识，则返回 -1 表示未知或无人是地主。
 *
 * @returns {number} 
 *          - 返回值为 `PLAYER` 表示当前玩家是地主；
 *          - 返回值为 `PLAYER_DOWN` 表示下家是地主；
 *          - 返回值为 `PLAYER_UP` 表示上家是地主；
 *          - 返回值为 `-1` 表示未找到地主标识。
 */
function findLandlordPosition() {
    var result = image.find("玩家地主标识");
    if (result.code == 0) {
        return PLAYER;
    }
    result = image.find("下家地主标识");
    if (result.code == 0) {
        return PLAYER_DOWN;
    }
    result = image.find("上家地主标识");
    if (result.code == 0) {
        return PLAYER_UP;
    }
    return -1;
}
/**
 * recognizeCards - 在指定范围内检测并返回牌的列表。
 *
 * 该函数根据给定的前缀 (`prefix`) 和范围 (`range`)，遍历所有可能的牌 (`all_cards`)，
 * 并通过调用 `image.find` 方法在图像中查找匹配的牌。如果找到匹配项，则将对应的牌添加到结果列表中。
 * 还有优化空间 可以通过检测重叠，以相似度高的为准。 可以防止识别出错
 * @param {string} prefix - 图像识别时使用的前缀字符串，用于构造匹配的图像标识符。
 *                          例如，`prefix + all_cards[i]` 将生成具体的图像查找目标。
 * @param {object} range - 指定卡牌识别的搜索范围
 * @returns {Array<string>} - 返回一个包含所有识别到的牌的数组。如果没有找到任何牌，则返回空数组。
 *
 * 示例：
 * ```javascript
 * let detectedCards = recognizeCards("card_", { 0,  0, 100, 200 });
 * console.log(detectedCards); // 输出类似 ["A", "K", "Q"] 的牌列表
 * ```
 */
function recognizeCards(prefix, range) {
    let cards = [];
    for (let i = 0; i < all_cards.length; ++i) {
        var result = image.find(prefix + all_cards[i], range);
        if (result.code != 0) {
            continue;
        }
        for (let j = 0; j < result.rects.length; ++j) {
            cards.push(all_cards[i]);
        }
    }
    return cards;
}

/**
 * 获取底牌
 */
function getLandlordBottomCards() {
    return recognizeCards("底牌-", 底牌位置);
}
/**
 * 获取玩家手牌
 */
function getPlayerHandCards() {
    return recognizeCards("", 玩家手牌位置);
}

/**
 * 等待游戏开始，通过识别出牌按钮，或者要不起按钮判断游戏是否开始
 */
function wait_for_start() {
    while (true) {
        updateFrame();     
        checkCloseButton() ;
        if(image.click("再来一局").code == 0){
            continue ;
        }
        if(image.click("再来一局2").code == 0){
            continue ;
        }
        //叫地主或者抢地主
        if(image.click("叫地主").code == 0){
            continue ;
        }
        if(image.click("抢地主").code == 0){
            continue ;
        }
        var result = image.find("出牌-灰");
        if (result.code == 0) {
            return true;
        }
        var result = image.find("首轮出牌-灰");
        if (result.code == 0) {
            return true;
        }
        var result = image.find("要不起");
        if (result.code == 0) {
            return true;
        }
        sleep(1)
    }
}

/**
 * 检查是否有关闭按钮，如果有则点击关闭按钮
 */
function checkCloseButton(){
    if(image.click("关闭按钮",关闭查找区域).code == 0){
        return true ;
    }
    if(image.click("关闭按钮2",关闭查找区域).code == 0){
        return true ;
    }
    return false ;
}
/**
 * 检测是否到玩家出牌
 * @returns 
 */
function checkIsPlayerAction() {
    var result = image.find("出牌-灰");
    if (result.code == 0) {
        return true;
    }
    var result = image.find("首轮出牌-灰");
    if (result.code == 0) {
        return true;
    }
    var result = image.find("要不起");
    if (result.code == 0) {
        return true;
    }
    return false;
}

/**
 * 智能体初始化
 * @param {Number} player_position 玩家位置
 * @param {Array} player_hand_cards 玩家手牌  
 * @param {Array} three_landlord_cards 底牌 
 */
function agentInit(player_position, player_hand_cards, three_landlord_cards) {
    var url = base_url + "/agent_init";
    player_hand_cards = player_hand_cards.map(card => RealCard2EnvCard[card]);
    three_landlord_cards = three_landlord_cards.map(card => RealCard2EnvCard[card]);
    var postData = {
        'key': key,
        'player_position': player_position,
        'player_hand_cards': player_hand_cards.join(','),
        'three_landlord_cards': three_landlord_cards.join(',')
    };
    var result = http.post(url, postData);
    console.log("agent init result:" + result);
}

/**
 * 上报玩家出牌信息
 * @param {Number} player_position 玩家位置
 * @param {Array} player_hand_cards 玩家出牌 
 */
function reportPlayerAction(player_position, played_cards) {
    console.log("player_action:" + played_cards);
    var url = base_url + "/action";
    played_cards = played_cards.map(card => RealCard2EnvCard[card]);
    var postData = {
        'key': key,
        'player_position': player_position,
        'played_cards': played_cards.join(',')
    };
    var result = http.post(url, postData);
    console.log("player_actionresult:" + result);
}

/**
 * 获取AI提供的出牌信息
 * @returns 
 */
function get_obs() {
    var url = base_url + "/get_obs";
    url += "?";
    url += ("key=" + key);
    var result = http.get(url);
    console.log("get_obs result:" + result);
    result = JSON.parse(result);
    if (result.code == 0) {
        return result.data.map(card => EnvCard2RealCard[card]);
    } else {
        throw new Error("agent return error:" + result.message);
    }
}
/**
 * 游戏结束上报
 */
function gameFinished(){
    var url = base_url + "/finish";
    var postData = {
        'key': key
    };
    var result = http.post(url, postData);
    console.log("game_finished:" + result);
}
/**
 * 根据地主位置生成玩家位置的映射关系。
 *
 * 该函数接收一个参数 `landlord_position`，表示当前游戏中地主所在的玩家位置，
 * 并返回一个数组（字典），描述每个玩家相对于地主的身份标识：
 * 
 * - 'landlord': 地主本人。
 * - 'landlord_down': 地主的下家。
 * - 'landlord_up': 地主的上家。
 *
 * @param {number} landlord_position - 地主的位置，取值为常量 `PLAYER`, `PLAYER_DOWN` 或 `PLAYER_UP`。
 *                                     如果传入其他值，将返回一个空数组。
 * @returns {Array} - 返回一个数组，索引对应玩家位置（`PLAYER`, `PLAYER_DOWN`, `PLAYER_UP`），
 *                    值为字符串，表示该玩家的身份标识。
 *
 * 示例：
 * - 输入：get_players_position_dict(PLAYER)
 *   输出：[ 'landlord', 'landlord_down', 'landlord_up' ]
 * 
 * - 输入：get_players_position_dict(PLAYER_DOWN)
 *   输出：[ 'landlord_up', 'landlord', 'landlord_down' ]
 */
function getPlayerRolesByLandlord(landlord_position) {
    let result = [];
    switch (landlord_position) {
        case PLAYER:
            result[PLAYER] = 'landlord';
            result[PLAYER_DOWN] = 'landlord_down';
            result[PLAYER_UP] = 'landlord_up';
            break;
        case PLAYER_DOWN:
            result[PLAYER] = 'landlord_up';
            result[PLAYER_DOWN] = 'landlord';
            result[PLAYER_UP] = 'landlord_down';
            break;
        case PLAYER_UP:
            result[PLAYER] = 'landlord_down';
            result[PLAYER_DOWN] = 'landlord_up';
            result[PLAYER_UP] = 'landlord';
            break;
    }
    return result;
}

/**
 * 获取数组arr1在arr2中不存在的元素。
 */
function extractUnique(arr1, arr2) {
    const countMap = {};
    // 统计arr2中元素的次数
    arr2.forEach(element => {
        countMap[element] = (countMap[element] || 0) + 1;
    });

    const result = [];
    // 遍历arr1，根据计数排除元素
    arr1.forEach(element => {
        if (countMap[element] > 0) {
            countMap[element]--;
        } else {
            result.push(element);
        }
    });

    return result;
}

/**
 * 执行玩家出牌逻辑
 * 根据AI提供的出牌建议，自动选择并提交出牌。
 */
function playerAction() {
    // 如果检测到“要不起”按钮，则直接返回空数组表示不出牌
    if (image.click("要不起").code == 0) {
        return [];
    }
    var obs_cards = get_obs(); //获取ai提供的出牌信息

    // 如果AI提供的信息为空，则点击“不出牌”按钮并返回空数组
    if (obs_cards.length == 0) {
        auto.toast("AI提示:不出", 0, 0);
        image.click("不出牌");
        return [];
    } else {
        auto.toast("AI提示:" + obs_cards, 0, 0);
    }
    // 第一步：根据AI建议选择所有需要的牌
    while (true) {
        updateFrame();
        var selectedCards = recognizeCards("", 已选牌位置);
        var remainingCardsToSelect = extractUnique(obs_cards, selectedCards);
        if (remainingCardsToSelect.length == 0) {
            break;
        }
        console.log("未选牌：" + remainingCardsToSelect);
        console.log("已选牌：" + selectedCards);
        image.click(remainingCardsToSelect[0], 玩家手牌位置);
        sleep(0.5);
    }
    // 第二步：检查是否有多选的牌并取消多余的选择
    while (true) {
        updateFrame();
        var selectedCards = recognizeCards("", 已选牌位置);
        var extraSelectedCards = extractUnique(selectedCards, obs_cards);
        if (extraSelectedCards.length == 0) {
            sleep(0.5);
            break;
        }
        console.log("errorCards:" + extraSelectedCards);
        image.click(extraSelectedCards[0], 已选牌位置);
        sleep(0.5);
    }
    updateFrame();
    var click_result = image.click("出牌-亮");
    if (click_result.code != 0) {
        click_result = image.click("首轮出牌-亮")
    }
    return obs_cards;
}
/**
 * 获取其他玩家出牌信息
 * 通过出牌位置颜色检查，排除出牌动画以及各种特效干扰
 */
function getPlayerAction(range, passImageName){
    const MAX_DETECTION_TIME = 5000; //最大检测时间,如果真实玩家对战 此时间需调高
    const COLOR_THRESHOLD = 50 ;//容错范围
    const STABILITY_DURATION = 500 ;//超时时间
    let startTime = Date.now();
    let previousCardColorCount = -1 ; //上一次当前角色出牌区域识别白色数量，用于判断是否在变化,防止出牌动画干扰
    let = colorStableStart = 0 ;
    let = colorStableDuration = 0 ;
    let action = [] ;
    while (true) { 
        if(Date.now() - startTime > MAX_DETECTION_TIME){
            break;
        }
        sleep(0.2);
        updateFrame();
        checkCloseButton() ;
        var result = image.find(passImageName)
        if (result.code == 0) {
            break ;
        }
        let cardBaseColorCount = color.count("#eae9e8",range,0.9) ; //卡牌底色数量
        if(cardBaseColorCount < 100){ //小于100认为还未出牌
            colorStableStart = 0 ;
            colorStableDuration = 0 ;
            continue ;
        }
        var ret = Math.abs(previousCardColorCount - cardBaseColorCount) < COLOR_THRESHOLD ;
        if(ret){
            if(colorStableStart == 0){
                colorStableStart = Date.now(); 
                colorStableDuration = 0 ;
            }else{
                colorStableDuration = Date.now() - colorStableStart ;
            }
        }else{
            colorStableStart = 0 ;
            colorStableDuration = 0 ;
        }
        previousCardColorCount = cardBaseColorCount ;
        if(colorStableDuration > STABILITY_DURATION){            
            action = recognizeCards("出牌-", range);
            break ;
        }
    }
    return action ;
}
function actionStep(landlordPosition,playerRoleMapping){
    var currentPlayerPosition = landlordPosition;
    var previousPlayPosition = -1; //上一次出牌的位置
    while (true) {
        updateFrame();
        

        if(image.click("再来一局").code == 0){
            gameFinished() ;
            break ;
        }
        
        if(image.click("再来一局2").code == 0){
            gameFinished() ;
            break ;
        }
        var playedCards;
        if (currentPlayerPosition > 2) {
            currentPlayerPosition = 0;
        }
        if (previousPlayPosition != currentPlayerPosition) {
            previousPlayPosition = currentPlayerPosition;
            switch (currentPlayerPosition) {
                case PLAYER:
                    console.log("等待玩家出牌");
                    break;
                case PLAYER_DOWN:
                    console.log("等待下家出牌");
                    break;
                case PLAYER_UP:
                    console.log("等待上家出牌");
                    break;
    
            }
        }
        if (currentPlayerPosition == PLAYER && checkIsPlayerAction()) {
            playedCards = playerAction();
            reportPlayerAction(playerRoleMapping[currentPlayerPosition], playedCards);  
            currentPlayerPosition++ ;
            sleep(0.2) ;
            continue ;
        }
        var range ;
        var passImageName ;
        switch (currentPlayerPosition) {
            case PLAYER: //玩家出牌显示时间较短 不容易识别，直接按ai提示上报
                // var result = image.find("玩家不出")
                // if(result.code == 0){
                //     console.log("玩家不出") ;
                //     passed = true ;
                // }else{
                //     playedCards= getPlayerPlayAction() ;
                // }
                break;    
            case PLAYER_DOWN:
                range = 下家出牌位置 ;
                passImageName = "下家不出" ;
                break;
            case PLAYER_UP:
                range = 上家出牌位置 ;
                passImageName = "上家不出" ;
                break;
        }
        playedCards = getPlayerAction(range, passImageName) ;
        reportPlayerAction(playerRoleMapping[currentPlayerPosition], playedCards);  
        currentPlayerPosition++ ;
        sleep(0.5);
    }
}

function startGame() {

    console.log("等待游戏开始 ....");
    wait_for_start();
    console.log("游戏已经开始 ....");
    var landlordPosition = findLandlordPosition();
    if (landlordPosition == -1) {
        console.log("未发现地主信息，结束任务");
        throw new Error("not found landlord ");
    }
    console.log("地主位置: " + landlordPosition);
    console.log("识别底牌信息 ....");
    var three_landlord_cards = [];
    while (true) {
        updateFrame();
        three_landlord_cards = getLandlordBottomCards();
        if (three_landlord_cards.length == 3) {
            break;
        }
    }
    console.log("底牌信息识别成功 " + three_landlord_cards);
    console.log("识别玩家手牌信息 ....");
    var player_hand_cards = [];
    hand_cards_count = landlordPosition == PLAYER ? 20 : 17;
    while (true) {
        updateFrame();
        player_hand_cards = getPlayerHandCards();
        if (player_hand_cards.length == hand_cards_count) {
            break;
        }
    }
    console.log("玩家手牌信息识别成功 " + player_hand_cards);
    var playerRoleMapping = getPlayerRolesByLandlord(landlordPosition); //玩家位置对应的角色信息

    agentInit(playerRoleMapping[PLAYER], player_hand_cards, three_landlord_cards);  //智能体初始化
    actionStep(landlordPosition,playerRoleMapping) ;
}

while(true) {
    startGame() ;
    console.log("游戏结束")
}

